﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using EzDesk.Utility;

namespace EzDesk.EzAppApi.Utility
{
    internal sealed class CacheEntry<TKey, T>
    {
        private readonly TKey _key;

        private T _value;

        private readonly Func<TKey, T> _valueProvider;

        private readonly object _updateValueSyncRoot = new object();

        private DateTime _lastUpdate = DateTime.MinValue;

        public T Value
        {
            get
            {
                if(IsExpired)
                {
                    // Acquire the lock and update value.
                    lock(_updateValueSyncRoot)
                    {
                        if(IsExpired)
                        {
                            // Update value.
                            _value = _valueProvider(_key);
                            _lastUpdate = DateTime.Now;
                        }
                        // Double check.
                    }
                }
                return _value;
            }
        }

        public bool IsExpired
        {
            get { return DateTime.Now - _lastUpdate > _interval; }
        }

        public void Expire()
        {
            _lastUpdate = DateTime.MinValue;
        }

        public CacheEntry(TKey key, Func<TKey, T> valueProvider, TimeSpan interval)
        {
            _valueProvider = valueProvider;
            _interval = interval;
            _key = key;
        }

        private readonly TimeSpan _interval;
    }


    class CachePool<TKey, T>
    {
        private readonly ConcurrentDictionary<TKey, CacheEntry<TKey, T>> _cache
            = new ConcurrentDictionary<TKey, CacheEntry<TKey, T>>();

        public T Retrieve(TKey key, Func<TKey, T> retrieval)
        {
            //            var caller = new StackTrace().GetFrame(1).GetMethod().Name;
            if(_cache.Count > _capacity)
            {
                // Do garbage collection.
                foreach(var cacheEntry in _cache)
                {
                    if(cacheEntry.Value.IsExpired)
                    {
                        CacheEntry<TKey, T> removedEmtry;
                        _cache.TryRemove(cacheEntry.Key, out removedEmtry);
                    }
                }
            }
            return _cache.GetOrAdd(key, p => new CacheEntry<TKey, T>(key, retrieval, _interval)).Value;
        }

        private readonly TimeSpan _interval;

        public TimeSpan Interval
        {
            get { return _interval; }
        }

        // TODO: Implement Max Entry Count feature.
        private readonly int _capacity;

        public int Capacity
        {
            get { return _capacity; }
        }


        public CachePool(TimeSpan interval, int capacity)
        {
            _interval = interval;
            _capacity = capacity == 0 ? int.MaxValue : capacity;
        }

        public void Expire()
        {
            foreach(var cacheEntry in _cache)
            {
                cacheEntry.Value.Expire();
            }
        }
    }

    internal class CachePoolCollection
    {
        private readonly ConcurrentDictionary<string, object> _cachePools =
            new ConcurrentDictionary<string, object>();

        public CachePool<TKey, T> Get<TKey, T>()
        {
            return Get<TKey, T>(new StackTrace().GetFrame(1).GetMethod().Name);
        }

        public CachePool<TKey, T> Get<TKey, T>(string name)
        {
            return Get<TKey, T>(name, () => new TimeSpan(0, 5, 0), 100);
        }

        public CachePool<TKey, T> Get<TKey, T>(string name, Func<TimeSpan> intervalProvider, int maxEntryCount)
        {
            return _cachePools.GetOrAdd(name,
                p => new CachePool<TKey, T>(intervalProvider(), maxEntryCount)) as CachePool<TKey, T>;
        }
    }
}
